package com.noahsloan.groovy.algorithms
import java.util.Map
/**
 * @author noah
 *
 */
public class GraphBuilder extends BuilderSupport {
	
	Closure createGraphNode = GraphAlgorithms.createNode
	def nodes = [:]
	def edges = new LinkedHashSet()
	
	/* (non-Javadoc)
	 * @see groovy.util.BuilderSupport#setParent(java.lang.Object, java.lang.Object)
	 */
	public void setParent(Object parent, Object child){
		switch(parent.type) {
		case 'node':
			switch(child.type) {
			case 'node':
				edges << parent.node.connect(child.node)
				return
			case 'edge':
				return
			case 'entry':
				parent.node[child.name] = child.value
				return
			}
			break
		case 'edge':
			if(child.type == 'entry') {
				parent.edge[child.name] = child.value
				return
			}
		}
		throw new RuntimeException("${child} is not a valid node at this location.")
	}
	
	protected Object doInvokeMethod(String methodName, Object name, Object args) {
		try {
			return super.doInvokeMethod(methodName,name,args)
		} catch(MissingMethodException e) {
			// XXX if a missing method exception is thrown, something above this
			// will mistreat it and claim that the builder is the source, 
			// even if it isn't
			throw new RuntimeException(e)
		}
	}
	
	/* (non-Javadoc)
	 * @see groovy.util.BuilderSupport#createNode(java.lang.Object)
	 */
	public Object createNode(Object name){
		return createNode(name,(Object)null)
	}
	
	/* (non-Javadoc)
	 * @see groovy.util.BuilderSupport#createNode(java.lang.Object, java.lang.Object)
	 */
	public Object createNode(Object name, Object value){
		return createNode(name,[:],value)
	}
	
	/* (non-Javadoc)
	 * @see groovy.util.BuilderSupport#createNode(java.lang.Object, java.util.Map)
	 */
	public Object createNode(Object name, Map attributes){
		return createNode(name,attributes,null)
	}
	
	/* (non-Javadoc)
	 * @see groovy.util.BuilderSupport#createNode(java.lang.Object, java.util.Map, java.lang.Object)
	 */
	public Object createNode(Object name, Map attributes, Object value){
		if(['node','edge'].any { it == name }) {
			// create a node for lookup
			def node = createGraphNode(value,attributes)
			// if the node exists, reuse it, otherwise store it
			if(nodes.containsKey(node)) {			
				node = nodes[node]
				// apply attributes to old node
				attributes?.each { node[it.key] = it.value }
			} else {
				nodes[node] = node
			}
			switch(name) {
				case "node":
					return [type:'node',node:node] 
				case "edge":
					if(current.type != 'node') {
						throw new RuntimeException("You are creatinging an edge"+
								" somewhere other than inside a node. You can't do that.")
					}
					def edge = current.node.connect(node)
					edges << edge
					return [type:'edge',edge:edge]
			}		
		} else {
			return [type:'entry',name:name,value:value]
		}
	}
	
}